package com.lucasx.util;

import com.lucasx.entity.*;
import com.lucasx.mapper.BasicDataNewOneMapper;
import com.lucasx.mapper.StationMapper;
import org.apache.commons.io.FileUtils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.DocumentException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created by Administrator on 2016/10/6.
 * <p>
 * 数据模拟Class
 *
 * @author LucasX
 */
public class DataSimulator {

    public static SqlSession sqlSession;
    public static final String WIND_XML_PATH = "wind.xml";
    private static List<Direction> directionList;
    private static final Logger logger = LogManager.getLogger(DataSimulator.class);

    static {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
            directionList = FileUtil.getDirectionList(FileUtil.readXml(DataSimulator.WIND_XML_PATH,
                    "directions"));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
    }


    /**
     * 将实体信息写入数据库
     *
     * @param basicDataNewOne
     * @throws IOException
     * @since V1.0
     */
    public static void insertBean(BasicDataNewOne basicDataNewOne) throws IOException {
        int i = sqlSession.getMapper(BasicDataNewOneMapper.class).insertSelective(basicDataNewOne);
        if (i > 0) {
            logger.debug("Insert successfully!");
        } else {
            logger.error("Insert fails!");
        }
//        sqlSession.close();

    }

    /**
     * 批量插入一天的数据
     *
     * @param basicDataNewOneList
     * @since V2.0
     */
    public static void insertBeanListPerDay(List<BasicDataNewOne> basicDataNewOneList) {
        int i = sqlSession.getMapper(BasicDataNewOneMapper.class).insertBasicDataNewOneList(basicDataNewOneList);
        if (i > 0) {
            logger.debug("A collection of data in one day has been inserted successfully~");
        } else {
            logger.error("A collection of data in one day has been inserted failed!!!");
        }
    }

    /**
     * 根据季节、月份、时间，建立规则库，模拟加载数据，填充BasicDataNewOne构造方法，并返回BasicDataNewOne对象
     *
     * @param observeDate
     * @param velocity
     * @param direction
     * @return
     * @since V1.0
     */
    @Deprecated
    public static BasicDataNewOne simulate(Date observeDate, Velocity velocity,
                                           Direction direction) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(observeDate);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyMMddHHmmss");
        short temperature, maxTemperature, minTemperature, precipitation, relhumidity;
        if (calendar.get(Calendar.MONTH) >= 3 && calendar.get(Calendar.MONTH) <= 5) {        //spring
            temperature = (short) (3 + Math.random() * 10 + 1);
            maxTemperature = (short) (10 + Math.random() * 2 + 1);
            minTemperature = (short) (3 + Math.random() * 2 + 1);
            precipitation = (short) (20 + Math.random() * 10);
            relhumidity = (short) (40 + Math.random() * 20);
        } else if (calendar.get(Calendar.MONTH) >= 6 && calendar.get(Calendar.MONTH) <= 8) { //summer
            temperature = (short) (18 + Math.random() * 10 + 1);
            maxTemperature = (short) (25 + Math.random() * 3 + 1);
            minTemperature = (short) (18 + Math.random() * 3 + 1);
            precipitation = (short) (30 + Math.random() * 10);
            relhumidity = (short) (70 + Math.random() * 10);
        } else if (calendar.get(Calendar.MONTH) >= 9 && calendar.get(Calendar.MONTH) <= 11) { //autumn
            temperature = (short) (15 + Math.random() * 10 + 1);
            maxTemperature = (short) (23 + Math.random() * 3 + 1);
            minTemperature = (short) (15 + Math.random() * 3 + 1);
            precipitation = (short) (5 + Math.random() * 5);
            relhumidity = (short) (40 + Math.random() * 20);
        } else {                                                                              //winter
            temperature = (short) (-1 + Math.random() * 10 + 1);
            maxTemperature = (short) (8 + Math.random() * 2 + 1);
            minTemperature = (short) (-1 + Math.random() * 2 + 1);
            precipitation = (short) (20 + Math.random() * 10);
            relhumidity = (short) (10 + Math.random() * 10);
        }

        Calendar tempCalendar = Calendar.getInstance();
        tempCalendar.setTime(new Date());
        BasicDataNewOne basicDataNewOne = new BasicDataNewOne(null,
                null, observeDate, simpleDateFormat.format(new Date(observeDate.getTime() + (long) (30000 * Math
                .random()))),
                CalcUtil.randNum(direction.getStartAngle(), direction.getToAngle()), CalcUtil.randNum
                (velocity.getMinVelocity(), velocity.getMaxVelocity()),
                (short) (CalcUtil.randNum(direction.getStartAngle(), direction.getToAngle()) + Math.random() * 10),
                (short) (CalcUtil.randNum(velocity.getMinVelocity(), velocity.getMaxVelocity()) + Math.random() * 10)
                , (short) (direction.getToAngle() + Math
                .random() * 5), (short) (CalcUtil.randNum(velocity.getMinVelocity(), velocity.getMaxVelocity()) +
                Math.random() * 5), (int) (Math.random() * 24) + ":" + (int) (Math.random() * 60),
                (short)
                        (direction.getStartAngle() + Math.random() * 15)
                , (short) (CalcUtil.randNum(velocity.getMinVelocity(), velocity.getMaxVelocity()) + Math.random() *
                20), (short) (direction.getStartAngle() + Math.random() * 10), (short) (velocity.getMaxVelocity()
                + Math
                .random() * 15), String.valueOf(tempCalendar.get(Calendar.HOUR) + ":" + tempCalendar.get(Calendar
                .MINUTE)), precipitation, null, temperature, maxTemperature, (int) (12 + Math.random() * 2) + ":" +
                (int)
                        (Math.random() * 60),
                minTemperature, (int) (Math.random() * 4) + ":" + (int)
                (Math.random() * 60)
                , relhumidity,
                (short) (relhumidity + Math.random() * 5), String.valueOf(tempCalendar.get(Calendar.HOUR) + ":" +
                tempCalendar
                        .get(Calendar
                                .MINUTE)), (short) (500 + Math.random() * 600), null, (short) (500 + Math.random() *
                600),
                (short) (800 + Math.random() * 20), null, (short) (500 + Math.random() * 20),
                null, null, (int) (1000 + Math.random() * 4000), (short) (temperature + Math.random() * 5), null,
                null, null, null, null, null,
                null, null, null, null);

        return basicDataNewOne;
    }

    /**
     * 输入yyyyMMdd的值, 返回一整天的数据模拟BasicDataNewOne对象列表
     *
     * @param lishiWeather
     * @return
     * @since V2.0
     */
    public static List<BasicDataNewOne> simulateFromHisData(LishiWeather lishiWeather, Station station) throws
            IllegalAccessException, IOException, InvocationTargetException, DocumentException {
        List<BasicDataNewOne> basicDataNewOneList = new ArrayList<>();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyMMddHHmmss");

        for (Date date : DataSimulator.genDate(lishiWeather.getDate())) {

            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            short precipitation = judgeParamsByMonth(calendar)[0], relhumidity = judgeParamsByMonth(calendar)[1];
            SimpleDateFormat minSecSimpleDateFormat = new SimpleDateFormat("mm:ss");

            BasicDataNewOne basicDataNewOne = new BasicDataNewOne(null, station.getStationnum(), date,
                    simpleDateFormat.format(new Date(date.getTime() + (long) (30000 * Math.random()))), (short)
                    TransUtil.convertDirection
                            (lishiWeather.getWindDirect().trim()), CalcUtil.randNum
                    (TransUtil.convertVelocity(lishiWeather.getWindForce())[0], TransUtil.convertVelocity
                            (lishiWeather.getWindForce())[1]), (short) TransUtil.convertDirection
                    (lishiWeather.getWindDirect().trim()), (short) (CalcUtil.randNum(TransUtil.convertVelocity
                    (lishiWeather.getWindForce())[0], TransUtil.convertVelocity(lishiWeather.getWindForce())[1]) +
                    Math.random() * 10), (short) TransUtil.convertDirection
                    (lishiWeather.getWindDirect().trim()), (short) (CalcUtil.randNum(TransUtil.convertVelocity
                            (lishiWeather.getWindForce())[0],
                    TransUtil.convertVelocity(lishiWeather.getWindForce())[1]) + Math.random() * 5),
                    minSecSimpleDateFormat.format(date.getTime()
                            + Math
                            .random() * 300000L), (short) TransUtil.convertDirection(lishiWeather.getWindDirect()
                    .trim()),
                    (short) (CalcUtil.randNum(TransUtil.convertVelocity(lishiWeather.getWindForce())[0], TransUtil
                            .convertVelocity(lishiWeather.getWindForce())[1]) + Math.random
                            () * 20),
                    (short) TransUtil.convertDirection(lishiWeather.getWindDirect().trim()), (short)
                    (TransUtil.convertVelocity(lishiWeather.getWindForce())[1] + Math.random() * 15),
                    minSecSimpleDateFormat.format(new Date()),
                    precipitation, null, CalcUtil.randNum(lishiWeather.getMinTemprature(), lishiWeather
                    .getMaxTemprature()), (short) lishiWeather
                    .getMaxTemprature(), minSecSimpleDateFormat.format(date.getTime() + Math.random() * 300000L),
                    (short) lishiWeather.getMinTemprature(), minSecSimpleDateFormat.format(new Date()), relhumidity,
                    (short) (relhumidity + Math.random() * 5), minSecSimpleDateFormat.format(new
                    Date()), (short) (500 + Math.random() * 600), null, (short)
                    (500 + Math.random() * 600), (short) (800 + Math.random() * 20), null,
                    (short) (500 + Math.random() * 20), null, null, (int) (1000 + Math.random() * 4000), CalcUtil
                    .randNum(lishiWeather.getMinTemprature(), lishiWeather.getMaxTemprature()),
                    null, null, null, null, null, null, null, null, null, null);

            basicDataNewOneList.add(basicDataNewOne);

            DataSimulator.outCsv("D:/basicInfo.csv", basicDataNewOne, false);
            System.gc();
        }

        return basicDataNewOneList;
    }

    /**
     * 根据月份判断precipitation, relhumidity的值
     *
     * @param calendar
     * @return short数组
     */
    private static short[] judgeParamsByMonth(Calendar calendar) {
        short[] params = new short[2];
        if (calendar.get(Calendar.MONTH) >= 3 && calendar.get(Calendar.MONTH) <= 5) {         //spring
            params[0] = (short) (20 + Math.random() * 10);
            params[1] = (short) (40 + Math.random() * 20);
        } else if (calendar.get(Calendar.MONTH) >= 6 && calendar.get(Calendar.MONTH) <= 8) {  //summer
            params[0] = (short) (30 + Math.random() * 10);
            params[1] = (short) (70 + Math.random() * 10);
        } else if (calendar.get(Calendar.MONTH) >= 9 && calendar.get(Calendar.MONTH) <= 11) { //autumn
            params[0] = (short) (5 + Math.random() * 5);
            params[1] = (short) (40 + Math.random() * 20);
        } else {                                                                              //winter
            params[0] = (short) (20 + Math.random() * 10);
            params[1] = (short) (10 + Math.random() * 10);
        }

        return params;
    }

    /**
     * 输入：爬虫爬取的日期格式为yyyyMMdd
     * 输出：yyyyMMddHHmmss
     *
     * @param dateStr
     * @return
     * @apiNote 格式化一天的日期，使其输出的时间间隔为30s
     */
    public static Date[] genDate(String dateStr) {
        Date[] dates = new Date[2 * 60 * 24];                //一天的日期, 2/min * 60min/ hour * 24hour/day
        Calendar calendar = Calendar.getInstance();
        calendar.set(Integer.parseInt(dateStr.substring(0, 4)), Integer.parseInt(dateStr.substring(4, 6)) - 1,
                Integer.parseInt(dateStr.substring(6, 8)), 0, 0, 0);
        long dayStartTime = calendar.getTime().getTime();    //每一天的开始时间

        for (int i = 0; i < dates.length; i++) {
            dates[i] = new Date(dayStartTime);
            dayStartTime += 30 * 1000;
        }

        return dates;
    }

    /**
     * 数据模拟程序<b>入口</b>
     *
     * @param startDate
     * @param endDate
     * @param velocityList
     * @param directionList
     * @param station
     * @param outCsvPath
     * @throws IOException
     * @since V1.0
     */
    @Deprecated
    public static void process(Date startDate, Date endDate, List<Velocity> velocityList, List<Direction>
            directionList, Station station, String outCsvPath) throws IOException {

        long timeDuration = 0L;
        int randNumV = 0, randNumD = 0;
        while (startDate.before(endDate)) {
            if (timeDuration > 3600000) {
                randNumV = (int) (Math.random() * velocityList.size());
                randNumD = (int) (Math.random() * directionList.size());
                timeDuration = 0;
            }

            Velocity velocity = velocityList.get(randNumV);
            Direction direction = directionList.get(randNumD);

            BasicDataNewOne basicDataNewOne = simulate(startDate, velocity, direction);
            basicDataNewOne.setStationnum(station.getStationnum());
            try {
                DataSimulator.insertBean(basicDataNewOne);
                DataSimulator.outCsv(outCsvPath, basicDataNewOne, false);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            startDate.setTime(startDate.getTime() + 30000L); //恰好30s
            timeDuration += 30000L;
        }

        System.gc();
    }

    /**
     * 数据模拟程序入口
     *
     * @param lishiWeatherCsvPath
     * @throws IOException
     * @throws DocumentException
     * @since V2.0
     */
    public static void process(String lishiWeatherCsvPath) throws IOException, DocumentException {

        List<LishiWeather> lishiWeatherList = FileUtil.readCsv(lishiWeatherCsvPath);
        List<Station> stationList = sqlSession.getMapper(StationMapper.class).selectAll();

        Iterator<Station> stationIterator = stationList.iterator();
        while (stationIterator.hasNext()) {
            Station station = stationIterator.next();
            Iterator<LishiWeather> lishiWeatherIterator = lishiWeatherList.iterator();
            List<BasicDataNewOne> basicDataNewOneList;
            while (lishiWeatherIterator.hasNext()) {
                LishiWeather lishiWeather = lishiWeatherIterator.next();
                try {
                    basicDataNewOneList = DataSimulator.simulateFromHisData(lishiWeather, station);
                    DataSimulator.insertBeanListPerDay(basicDataNewOneList);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }

            }

            System.gc();
            logger.info(station.getStationnum() + " -- " + station.getStationname() + " 的数据写入CSV成功!");
        }

    }

    /**
     * 写出模拟生成的数据到CSV文件, isKeepHeader{true=保留第一行字段;false=不保留第一行字段}
     * <b>FORMAT = CSV， MODE = APPEND</b>
     *
     * @param outFileDestination
     * @param basicDataNewOne
     * @param isKeepHeader
     * @throws IOException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static void outCsv(String outFileDestination, BasicDataNewOne basicDataNewOne, boolean isKeepHeader) throws IOException,
            IllegalAccessException, InvocationTargetException {
        StringBuilder stringBuilder;
        Field[] fields = basicDataNewOne.getClass().getDeclaredFields();

        if (true == isKeepHeader) {
            stringBuilder = new StringBuilder("ID,StationNum,ObservTimes,InsertTime,WindDirect,WindVelocity,WindDirect10,WindVelocity10,MaxWindD,MaxWindV,TimeMaxWind," +
                    "InstantWindD,InstantWindV,ExMaxWindD,ExMaxWindV,TimeExMaxWind,Precipitation,PrecipitationFromHour,DryBulTemp,MaxTemp,TimeMaxTemp,MinTemp,TimeMinTemp," +
                    "RelHumidity,MinRelHumidity,TimeMinRelH,VapourPress,DewTemp,StationPress,MaxPSta,TimeMaxPSta,MinPSta,TimeMinPSta,SeaLevelPress,Visibility,SurfaceTemp," +
                    "Temp5cm,Temp10cm,Temp15cm,Temp20cm,Temp40cm,Temp80cm,Temp160cm,Temp320cm,SnowDeep,backup1\r\n");
        } else {
            stringBuilder = new StringBuilder();
        }

        for (int i = 0; i < fields.length - 1; i++) {
            for (Method method : basicDataNewOne.getClass().getMethods()) {
                if ((method.getName().startsWith("get"))) {
                    if (method.getName().toLowerCase().endsWith(fields[i].getName().toLowerCase())) {
                        stringBuilder.append(method.invoke(basicDataNewOne)).append(",");
                    }
                }
            }
        }
        stringBuilder.append(basicDataNewOne.getBackup1()).append("\r\n");

        FileUtils.writeStringToFile(new File(outFileDestination), stringBuilder.toString(), "UTF-8", true);
    }

    public static void main(String[] args) {
        try {
            DataSimulator.process("D:\\历史天气-嘉兴-2015.csv");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }
}